package internal

import (
	"github.com/name5566/leaf/gate"
	"reflect"
	"talent.com/agency_im/common/cxt"
	"talent.com/agency_im/common/interceptors"
	"talent.com/agency_im/common/msg"
	"talent.com/agency_im/common/user"
	"talent.com/agency_im/conf/g"
	"talent.com/server/durotar.git/leaf"
	"talent.com/server/durotar.git/log"
	"talent.com/server/durotar.git/proto"
	"talent.com/server/durotar.git/tools/tz"
	"time"
)

// 免登录验证的消息类型列表
var FreeAuthMsgTypes = map[reflect.Type]interface{}{
	reflect.TypeOf(&proto.Ping{}):  nil,
	reflect.TypeOf(&msg.AuthReq{}): nil,
}

// 绑定消息处理函数
func handler(m interface{}, h func(interface{}, gate.Agent, *cxt.UserContext)) {
	t := reflect.TypeOf(m)
	skeleton.RegisterChanRPC(t, func(args []interface{}) {
		var (
			msg = args[0]
			ag  = args[1].(gate.Agent)
			uc  = getUserContext(ag)
			uid uint64
		)
		if uc != nil {
			uid = uc.ID
		}

		log.Debug("receive msg, agent: [%p], userID:[%v], msgType: [%v], msg: [%+v]", ag, uid, t, msg)
		// 验证授权
		if _, ok := FreeAuthMsgTypes[t]; !ok {
			if uc == nil {
				log.Info("no auth access in agent: [%p]", ag)
				leaf.CloseAgent(ag, proto.STATUS_NOT_AUTH, "未登录授权", uid)
				return
			}
		}
		h(args[0], ag, uc)
	})
}

func init() {
	skeleton.RegisterChanRPC("NewAgent", rpcNewAgent)
	skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent)

	handler(&proto.Ping{}, pong)
	handler(&msg.AuthReq{}, handAuth)
	handler(&msg.InTopicReq{}, handInTopicReq)
	handler(&msg.ChatReq{}, handChatReq)
	handler(&msg.ChatNTAck{}, handChatNTAck)
}

func rpcNewAgent(args []interface{}) {
	agent := args[0].(gate.Agent)
	skeleton.AfterFunc(5*time.Second, func() {
		leaf.CheckAuth(agent)
	})
}

func rpcCloseAgent(args []interface{}) {
	ag := args[0].(gate.Agent)
	if ag.UserData() != nil {
		uc, ok := ag.UserData().(*cxt.UserContext)
		if !ok {
			return
		}
		log.Debug("user logout, agent: [%p], user: [%v]", ag, uc.ID)
		//g.AllAgents.Delete(uc.ID)
		//sendPresenceNT2Subscribers(uc.ID, msg.PRESENCE_TYPE_OFFLINE)
	}
}

func pong(message interface{}, ag gate.Agent, uc *cxt.UserContext) {
	if ag.UserData() != nil {
		uc, _ := ag.UserData().(*cxt.UserContext)
		uc.LastActiveAt = tz.GetNowTsMs()
	}

	ag.WriteMsg(&proto.Pong{
		Timestamp: tz.GetNowTsMs(),
	})
}

func handAuth(message interface{}, ag gate.Agent, uc *cxt.UserContext) {
	req := message.(*msg.AuthReq)
	var (
		u   *user.User
		err error
	)
	skeleton.Go(func() {
		u, err = user.Auth(req.Token)
	}, func() {
		if err == nil {
			onAuthSuccess(u, ag)
		} else {
			onAuthFail(ag, err)
		}
	})
}

func logout(uc *cxt.UserContext, ag gate.Agent) {
	g.AllAgents.Delete(uc.ID)
	ag.Close()
}

func onAuthSuccess(u *user.User, ag gate.Agent) {
	// 不支持多终端登录，踢掉老连接
	if v, ok := g.AllAgents.Load(u.ID); ok {
		existAg := v.(gate.Agent)
		log.Info("user login in new agent, old agent: [%p], new agent: [%p]", existAg, ag)
		leaf.CloseAgent(existAg, proto.STATUS_DUP_LOGIN, "该账号已在其它地方登录", u.ID)
	}

	ag.SetUserData(&cxt.UserContext{User: u, LastActiveAt: tz.GetNowTsMs()})
	g.AllAgents.Store(u.ID, ag)

	ag.WriteMsg(&msg.AuthResp{
		UserID:      u.ID,
		Nickname:    u.Nickname,
		PortraitUri: u.PortraitUri,
		Timestamp:   tz.GetNowTsMs(),
	})

	skeleton.Go(func() {
		// 推送离线消息
		notifyPushMessageByUserID(u.ID)
		// 推送在线状态给订阅者
		sendPresenceNT2Subscribers(u.ID, msg.PRESENCE_TYPE_ONLINE)
	}, nil)
}

func onAuthFail(ag gate.Agent, err error) {
	log.Info("login fail: %v", err.Error())
	leaf.CloseAgent(ag, proto.STATUS_UNKNOWN_ERROR, "登录失败", 0)
}

func handInTopicReq(message interface{}, ag gate.Agent, uc *cxt.UserContext) {
	req := message.(*msg.InTopicReq)
	if req.Topic.ID < 1 {
		log.Info("in topic id must > 0")
		return
	}

	// 执行会话开始勾子逻辑
	skeleton.Go(func() {
		if err := interceptors.OnBeginConversation(uc.ID, req.Topic.ID); err != nil {
			log.Error("onBeginConversation fail: %v", err)
		}
	}, nil)

	// 订阅
	subscribeUser(uc.ID, req.Topic.ID)
	// 通知对方在线状态
	sendPresenceNT(ag, req.Topic.ID)
}

func onMessageSendSuccess(ag gate.Agent, message *msg.Message) {
	if message.SendSelf {
		ag.WriteMsg(&msg.ChatReqAck{
			Topic:     &msg.Topic{TopicType: msg.TOPIC_TYPE(message.TopicType), ID: message.TargetID},
			ClientID:  message.ClientID,
			MsgID:     message.ID,
			Timestamp: message.CreatedAt.Unix(),
		})
	}
}

func handChatReq(message interface{}, ag gate.Agent, uc *cxt.UserContext) {
	req := message.(*msg.ChatReq)

	if req.Topic.ID < 1 {
		log.Info("chat topic id must > 0")
		return
	}

	var (
		imMsg *msg.Message
		err   error
	)
	skeleton.Go(func() {
		imMsg = &msg.Message{
			ClientID:  req.ClientID,
			TopicType: int8(msg.TOPIC_TYPE_PRIVATE),
			SourceID:  uc.ID,
			TargetID:  req.Topic.ID,
			MsgType:   int8(req.MsgType),
			Info:      req.Info,
			SendSelf:  true,
		}
		err = SendMessage(imMsg)
		if err != nil {
			log.Error("handle chatReq message fail, the msg: <%+v>, error: %v", req, err)
		}
	}, func() {
		if err == nil {
			onMessageSendSuccess(ag, imMsg)
		}
	})
}

func handChatNTAck(message interface{}, ag gate.Agent, uc *cxt.UserContext) {
	req := message.(*msg.ChatNTAck)

	skeleton.Go(func() {
		msg.UpdateTopicReadMsgID(uc.ID, msg.GetTopicID(int8(req.Topic.TopicType), uc.ID, req.Topic.ID), req.MaxMsgID)
	}, nil)
}
